/* Changes:
 *
 * added finally blocks to try blocks
 *   always put cleanup code (e.g. closing opened files) in finally blocks
 * added CLI argument support, removed hard-coded file names
 *   2 operating modes: stream operation and file operation, automatically 
 *   determined by number of arguments
 * removed requirement for named segments in input file 
 *   now complies with the standard gss text format ('x1 y1 x2 y2\n')
 *   program now generates internal id's (currently just an int index)
 * uses JRE properties to control output
 */
package gss;

import org.apache.commons.math.fraction.BigFraction;
import java.math.BigInteger;

import java.io.Writer;
import java.io.FileWriter;
import java.io.BufferedWriter;
import java.io.OutputStreamWriter;

import java.io.Reader;
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.InputStreamReader;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Vector;
import java.util.Scanner;

/**
 *
 * @author mdm32
 */
public class Main {

    /**
     * file extension for 0-1 matrix output
     */
    private static final String OUT = "matrix";

    /** Preprocessor reads a set of line segments, one per line via standard
     * input or a file (depending on argument), and outputs a 0-1 incidence
     * matrix of all vertices. If no arguments are given, all input and output
     * is done via standard input and output. The options Property must be set
     * to "quiet" or debugging garbage will get mixed in to the output. In file
     * mode, command line arguments specify the input file and optionally the
     * output file. If no output file is specified, the output file is obtained
     * by appending an extension to the input file. Output is a 0-1 * space-
     * separated incidence matrix.
     *
     * @param args Command line arguments: input_format [input_file [output_file]]
     *		input_format should be "f" for frac and "d" for double
     */
    public static void main(String[] args) throws
            FileNotFoundException, IOException {

        //test extend method
        /*LineSegment h = new LineSegment("test", 5, 5, 10, 10);
        h = h.extend(1, 0, 0, 20, 20);
        System.out.println(h);
         */
        

        if (args.length < 1) {
            System.err.println("Please specify input file format: f | d");
            System.exit(1);
        }

        /* if true, reads in "string / string"
         * if false, reads in "double"
         */
        boolean readsBigFrac = false;
        if (args[0].equals("f")) {
            readsBigFrac = true;
        } else if (args[0].equals("d")) {
            readsBigFrac = false;
        } else { // something wrong
            System.err.println("Unrecognized input file format; please use: f | d");
            System.exit(1);
        }

        //create slot for GSS structure to be held
        Vector<LineSegment> gssstructure = new Vector<LineSegment>();

        // process Properties (see http://java.sun.com/docs/books/tutorial/essential/environment/properties.html)
        String options = System.getProperty("options");
        if (options == null) {
            options = "";
        }

        // true to print debug output, on by default
        boolean printflag = !options.matches("quiet");

        // Process CLI arguments for INPUT ONLY
        Scanner scanner = null;
        String input_file = null;
        try {
            switch (args.length) {
                case 1:
                    //scanner = new Scanner(System.console().reader());
                    scanner = new Scanner(new BufferedReader(new InputStreamReader(System.in)));
                    break;
                default:
                    System.err.println("Warning: Extra arguments past the first 2 are ignored");
                case 2:
                case 3:
                    input_file = args[1];
                    scanner = new Scanner(new BufferedReader(new FileReader(input_file)));
                    break;
            }

            // declare some temporary variables
            int seg_id = 0;
            String cx1n, cx1d, cx2n, cx2d;
            String cy1n, cy1d, cy2n, cy2d;

            BigFraction cx1, cy1, cx2, cy2;
            // read in all data from file
            if (readsBigFrac) {
                while (scanner.hasNext()) {
                    String line = scanner.nextLine();
                    Scanner line_scanner = new Scanner(line);
                    String value = null;
                    java.util.regex.MatchResult matcher = null;

                    value = line_scanner.findInLine("(\\d+)(\\s*/\\s*(\\d+))?");
                    matcher = line_scanner.match();
                    cx1n = matcher.group(1);
                    cx1d = matcher.group(3);
                    if (cx1d == null || cx1d.equals("")) {
                        cx1d = "1";
                    }

                    value = line_scanner.findInLine("(\\d+)(\\s*/\\s*(\\d+))?");
                    matcher = line_scanner.match();
                    cy1n = matcher.group(1);
                    cy1d = matcher.group(3);
                    if (cy1d == null || cy1d.equals("")) {
                        cy1d = "1";
                    }

                    value = line_scanner.findInLine("(\\d+)(\\s*/\\s*(\\d+))?");
                    matcher = line_scanner.match();
                    cx2n = matcher.group(1);
                    cx2d = matcher.group(3);
                    if (cx2d == null || cx2d.equals("")) {
                        cx2d = "1";
                    }

                    value = line_scanner.findInLine("(\\d+)(\\s*/\\s*(\\d+))?");
                    matcher = line_scanner.match();
                    cy2n = matcher.group(1);
                    cy2d = matcher.group(3);
                    if (cy2d == null || cy2d.equals("")) {
                        cy2d = "1";
                    }

                    cx1 = new BigFraction(new BigInteger(cx1n), new BigInteger(cx1d));
                    cy1 = new BigFraction(new BigInteger(cy1n), new BigInteger(cy1d));
                    cx2 = new BigFraction(new BigInteger(cx2n), new BigInteger(cx2d));
                    cy2 = new BigFraction(new BigInteger(cy2n), new BigInteger(cy2d));

                    gssstructure.add(new LineSegment(Integer.toString(seg_id), cx1, cy1, cx2, cy2));
                    seg_id++;
                }

            } else {
                while (scanner.hasNext()) {
                    cx1 = new BigFraction(scanner.nextDouble());
                    cy1 = new BigFraction(scanner.nextDouble());
                    cx2 = new BigFraction(scanner.nextDouble());
                    cy2 = new BigFraction(scanner.nextDouble());
                    gssstructure.add(new LineSegment(Integer.toString(seg_id), cx1, cy1, cx2, cy2));
                    seg_id++;
                }
            }
        } finally {
            if (scanner != null) {
                scanner.close();
            }
        }

        /* print line segments */
        if (printflag) {
            for (LineSegment seg : gssstructure) {
                System.err.println(seg);
            }
            System.err.println("\n");
        }

        int[] a = parallelInt(gssstructure);
        while (a[0] != -1) {
            gssstructure.add(mergeParallel(gssstructure.elementAt(a[0]), gssstructure.elementAt(a[1])));
            gssstructure.removeElementAt(a[0]);
            gssstructure.removeElementAt(a[1] - 1);
            a = parallelInt(gssstructure);
        }

        for (int i = 0; i < gssstructure.size(); i++) {
            gssstructure.elementAt(i).setName("" + i);
        }

        // print segments after merged parallel intersecting
        if (printflag) {
            for (LineSegment seg : gssstructure) {
                System.err.println(seg);
            }
            System.err.println("\n");
        }

        //figure out the intersections
        Vector<Vertex> vertices = new Vector<Vertex>();
        boolean needtoadd = true;
        int m = gssstructure.size() - 1;
        for (int i = (gssstructure.size() - 1); i >= 0; i--) {
            for (int j = 0; j < m; j++) {
                if (gssstructure.elementAt(i).intersects(gssstructure.elementAt(j))) {
                    needtoadd = true;
                    Vertex temp = gssstructure.elementAt(i).intersection(gssstructure.elementAt(j));
                    for (int b = 0; b < vertices.size(); b++) {
                        if (temp.equals(vertices.elementAt(b))) {
                            vertices.setElementAt(temp.mergeVertex(vertices.elementAt(b)), b);
                            needtoadd = false;
                        }
                    }
                    if (needtoadd == true) {
                        vertices.add(temp);
                    }
                }
            }
            m--;
        }

        // print vertices
        if (printflag) {
            for (int i = 0; i < vertices.size(); i++) {
                //vertices.elementAt(i).printVertex();
                vertices.elementAt(i).printVertex2(gssstructure.size());
            }
            System.err.println("\n");
            for (int i = 0; i < vertices.size(); i++) {
                vertices.elementAt(i).printVertex();
            }
        }

        BufferedWriter out = null;
        try {
            FileWriter fstream = new FileWriter(input_file + ".intersect1.txt");
            out = new BufferedWriter(fstream);
            for (int i = 0; i < vertices.size(); i++) {
                out.write(vertices.elementAt(i).asString1() + "\n");
            }
        } catch (Exception e) {
            System.err.println("Error: " + e.getMessage());
        } finally {
            out.close();
        }
        // Process CLI arguments for OUTPUT ONLY
        Writer writer = null; // reference to the output stream, be it stdout or file
        String output_file = null;
        try {
            switch (args.length) {
                case 1:
                    //writer = System.console().writer();
                    writer = new BufferedWriter(new OutputStreamWriter(System.out));
                    break;
                case 2:
                    output_file = input_file + "." + OUT;  // use input file name + extension
                    writer = new BufferedWriter(new FileWriter(output_file));
                    break;
                default:
                case 3:
                    output_file = args[2];
                    writer = new BufferedWriter(new FileWriter(output_file));
                    break;
            }
            for (int i = 0; i < vertices.size(); i++) {
                writer.write(vertices.elementAt(i).asString2(gssstructure.size()) + "\n");
            }
        } catch (Exception e) {//Catch exception if any
            System.err.println("Error: " + e.getMessage());
        } finally {
            if (writer != null) {
                writer.close();
            }
        }
    }


    /*shoots back (n,m), the indices of the first two parallel segments
     * that intersect
     */
    public static int[] parallelInt(Vector<LineSegment> gss) {
        for (int i = 0; i < gss.size(); i++) {
            for (int j = 0; j < gss.size(); j++) {
                if (i != j) {
                    if (gss.elementAt(i).isParallelTo(gss.elementAt(j))) {
                        if (gss.elementAt(i).intersects(gss.elementAt(j))) {
                            int[] a = new int[2];
                            a[0] = i;
                            a[1] = j;
                            return a;
                        }
                    }
                }
            }
        }
        int[] a = new int[2];
        a[0] = -1;
        a[1] = -1;
        return a;
    }

    /* checks approximate distance between (x1,y1) and (x2,y2) and returns as double*/
    public static double distance(double x1, double y1, double x2, double y2) {
        return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
    }

    /*Assumes segments a and b are parallel and merges them into a single segment.
     * Returns this new segment
     */
    public static LineSegment mergeParallel(LineSegment a, LineSegment b) {
        BigFraction x1 = a.getX1();
        BigFraction y1 = a.getY1();
        BigFraction x2 = a.getX2();
        BigFraction y2 = a.getY2();
        BigFraction x3 = b.getX1();
        BigFraction y3 = b.getY1();
        BigFraction x4 = b.getX2();
        BigFraction y4 = b.getY2();

        double[] d = new double[6];
        d[0] = distance(x1.doubleValue(), y1.doubleValue(), x2.doubleValue(), y2.doubleValue());
        d[1] = distance(x1.doubleValue(), y1.doubleValue(), x3.doubleValue(), y3.doubleValue());
        d[2] = distance(x1.doubleValue(), y1.doubleValue(), x4.doubleValue(), y4.doubleValue());
        d[3] = distance(x2.doubleValue(), y2.doubleValue(), x3.doubleValue(), y3.doubleValue());
        d[4] = distance(x2.doubleValue(), y2.doubleValue(), x4.doubleValue(), y4.doubleValue());
        d[5] = distance(x3.doubleValue(), y3.doubleValue(), x4.doubleValue(), y4.doubleValue());

        double max = 0;
        int iter = 0;
        for (int i = 0; i < 6; i++) {
            if (d[i] >= max) {
                max = d[i];
                iter = i;
            }
        }

        if (iter == 0) {
            return new LineSegment("0", x1, y1, x2, y2);
        } else if (iter == 1) {
            return new LineSegment("1", x1, y1, x3, y3);
        } else if (iter == 2) {
            return new LineSegment("2", x1, y1, x4, y4);
        } else if (iter == 3) {
            return new LineSegment("3", x2, y2, x3, y3);
        } else if (iter == 4) {
            return new LineSegment("4", x2, y2, x4, y4);
        } else if (iter == 5) {
            return new LineSegment("5", x3, y3, x4, y4);
        }
        return new LineSegment("6", new BigFraction(0), new BigFraction(0), new BigFraction(0), new BigFraction(0));
    }
}
